禮拜四的凌晨,台北的天空霧濛濛的,彷彿預告著明天的壞天氣還有等不到週末的壞心情。
不過珍妮可不在意這些,今天是她重要的日子。
珍妮要灑錢打造屬於自己的一輛車,順手振興台灣經濟。她的心裡在考慮兩種車:轎車和跑車。但是珍妮自己不太懂車呀,於是她委託懂車的工頭/工人來負責製造。這個工頭告訴珍妮:轎車的零件有車體、引擎,而跑車的零件有車體、引擎、酷炫擾流板。而他旁邊這個工人很萬能,會安裝三樣零件:車體、引擎、酷炫擾流板。所以如果珍妮委託工頭/工人做一台轎車,工頭就會請工人安裝車體、安裝引擎;如果珍妮委託工頭/工人做一台跑車,工頭就會請工人安裝車體、安裝引擎、安裝酷炫擾流板。
這個例子中的工人就是 Builder,工頭則是 Director,而那些被製造的轎車和跑車稱作 Product。
比較正式點說,Builder 是一種創建型設計模式:任何複雜 object (稱作 product) 的創建過程都只是請工人安裝一連串的零件而已。
Builder 專門用在需要很多零件才能創建的複雜 product:把各種 product 的構成拆解成多個零件,builder 就像是工人,只負責執行安裝各個零件,但不知道哪些零件串在一起會做出什麼 product;而 director 就像是工頭,自己不會安裝任何零件,但負責記住哪幾個零件裝在一起可以做出什麼 product。所以當要創建某個 product,director 就會請 builder 安裝該 product 需要的所有零件。
兩個建構子(Constructor)各自負責轎車、跑車就好啦?
當有幾十種車時,就會有幾十種建構子,每個建構子裡面都有很類似的「安裝引擎」的功能,就造成「安裝引擎」的功能被重複寫了幾十次,以後每次改都要改幾十次。比較麻煩。
只要一個建構子負責接收 3 個參數:車體種類、引擎種類、擾流板種類,再根據接收的參數製造不同車種就好啦?
當要製造的車種類繁多時(包含計程車 垃圾車 救護車 靈車 閃電霹靂車...),假設總共有 10 個參數,大部分的轎車只要 2 個參數就能製造。那呼叫那個大建構子來製造大部分的車的時候,輸入參數的部分常常有 8 個欄位都是空的。比較醜。
此外的好處是,builder 同時也符合單一功能原則:複雜的安裝XX功能都被獨立於 builder 裡面了。
而壞處則來自於我們創造的額外 classes,以這種角度來看程式碼是變複雜了。
from abc import ABC
# 汽車
class Car():
def __init__(self):
self.parts = "A car with "
def add(self, part):
self.parts += (part + " ")
# 機車
class Motorcycle():
def __init__(self):
self.parts = "A motorcycle with "
def add(self, part):
self.parts += (part + " ")
# 抽象的builder會 (0)回傳product (1)安裝車體 (2)安裝引擎 (3)安裝擾流板
class Builder(ABC):
def get_product(self):
pass
def install_body(self):
pass
def install_engine(self):
pass
def install_spoiler(self):
pass
# 汽車builder會 (0)回傳product (1)安裝車體 (2)安裝引擎 (3)安裝擾流板
class CarBuilder(Builder):
# 初始化:產生空的汽車供安裝各種部件
def __init__(self):
self.reset()
# 產生空的汽車供安裝各種部件
def reset(self):
self._product = Car()
# [細節] 通常每個builder要負責提供方法獲取他製造的東西,因為不同builder可能回傳完全不同class的object,比如說CarBuilder製造Car,而MotorcycleBuilder製造Motorcycle
# 通常builder回傳製造的東西以後要重設,準備下一輪的安裝,所以通常會呼叫reset()
def get_product(self):
product = self._product
self.reset()
return product
def install_body(self):
self._product.add("car_body")
def install_engine(self):
self._product.add("car_engine")
def install_spoiler(self):
self._product.add("car_spoiler")
# 機車builder會 (0)回傳product (1)安裝車體 (2)安裝引擎 (3)安裝擾流板
class MotorcycleBuilder(Builder):
# 初始化:產生空的機車供安裝各種部件
def __init__(self):
self.reset()
# 產生空的機車供安裝各種部件
def reset(self):
self._product = Motorcycle()
def get_product(self):
product = self._product
self.reset()
return product
def install_body(self):
self._product.add("motorcycle_body")
def install_engine(self):
self._product.add("motorcycle_engine")
def install_spoiler(self):
self._product.add("motorcycle_spoiler")
# Director負責記得製造哪種車需要哪些零件,然後會呼叫builder負責安裝每個零件
class Director():
def __init__(self):
self._builder = None
# 用來存取builder,因為我們要負責指派builder給director
def builder(self):
return self._builder
# 普通車款要安裝車體、安裝引擎
def build_basic_entity(self):
self.builder.install_body()
self.builder.install_engine()
# 運動型車款要安裝車體、安裝引擎、安裝擾流板
def build_sports_entity(self):
self.builder.install_body()
self.builder.install_engine()
self.builder.install_spoiler()
if __name__ == "__main__":
# 假設現在要製造轎車跟跑車(兩種汽車)
director = Director()
car_builder = CarBuilder()
director.builder = car_builder
print("Constructing basic car")
director.build_basic_entity()
print("product completed:", car_builder.get_product().parts)
print("\n")
print("Constructing sports car")
director.build_sports_entity()
print("product completed:", car_builder.get_product().parts)
print("\n")
# [細節] 如果要客製化某台很特別的車(只有車體跟擾流板的展示用車),其實我們也可以不用director直接呼叫car_builder內部的安裝功能
print("Constructing custom car (without director)")
car_builder.install_body()
car_builder.install_spoiler()
print("product completed:", car_builder.get_product().parts)
print("\n")
# [細節] director記得運動型車款的零件菜單:如果交給他CarBuilder會做出四輪跑車,如果交給他MotorcycleBuilder會做出兩輪跑車
motorcycle_builder = MotorcycleBuilder()
director.builder = motorcycle_builder
print("Constructing sports motorcycle")
director.build_sports_entity()
print("product completed:", motorcycle_builder.get_product().parts)
在工頭/工人的幫助下,熱愛速度感的珍妮終於完成了她人生中第一輛跑車,也或多或少地振興了台灣經濟,可喜可賀。
更可喜可賀的是,你也堅持著看完今天的文章了。除了感謝閱讀,也希望你從今天的文章獲得一些想法,明天繼續一起學習 design pattern 吧!
那麼,明天就輪到 composite 登場啦!敬請期待!
作者:Allen